/* 
    <provides OS-like functionality using a time-triggered scheduler>
    Copyright (C) <2012>  <Jonathan D. Armstrong> <jonathan.armstrong AT mavs.uta.edu>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/**
 * @file   nortos.c
 * @Author Jonathan D. Armstrong (jonathan.armstrong@mavs.uta.edu)
 * @date   December, 2012
 * @brief  Provides access to basic OS-like functionality.
 *
 * init_task(), create_task(), delete_task(), delay_task(), etc.
 */
 
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>

#include "nortos.h"
#include "hardware.h"
#include "projectdefs.h"

/* store how many entries are present in task_state_t enumerated type */
const int task_state_t_size = (READY - INVALID) + 1;

/**
 * @name    Initialize tasks
 * @brief   Forces all entries in task control block (TCB) to be marked as INVALID state.
 * @ingroup Typically call init_tasks() before creating any tasks or entering nortos scheduler.
 *
 * This API provides certain actions as an example.
 *
 * @param [in] nothing  No input arguments - fixme.
 *
 * @retval Nothing.   No return value - fixme to provide failure code.
 *
 * Example Usage:
 * @code
 *    // initialize task control block so all tasks start as invalid
 *    //   (this is done after init_hardware() because on some ports
 *    //   memory where the TCB lives may need to be initialized)
 *    init_tasks();
 * @endcode
 */
/* init task control block */
void init_tasks(void)
{
    /* index variable */
    uint8_t i;
    
    /* flag variable */
    bool ok = true;
    
    /* go ahead and check here to see if MAX_TASKS
       is some sane value - might as well check here
       as this function should be called soon after
       program startup
    */
    ok &= 0 < MAX_TASKS;
    ok &= 100 >= MAX_TASKS;
    assert(ok);
    
    /* go through all task block entries */
    for (i = 0; i < MAX_TASKS; i++)
    {
        /* initialize to invalid */
        tasks[i].state = INVALID;
    }
}

/**
 * @name    Create Task
 * @brief   Creates an entry in the task control block (TCB) for a specified function.
 * @ingroup example
 *
 * Creates an entry in the task control block (TCB) for a specified function.
 *
 * @param [in] functionPtr  Pointer to function which will be called when task is in READY state.
 * @param [in] period       Number of milliseconds to delay between task runs.
 * @param [in] state        Default state of task upon creation (see task_state_t).
 *
 * @retval  0  Successfully created task.
 * @retval -1  No entries were available in TCB.
 *
 * Example Usage:
 * @code
 *    // create task which points to function blink_1hz()
 *    // and delays until run once per second
 *    status = create_task(&blink_1hz, 1, DELAYED);
 * @endcode
 */
/* create task */
int create_task(uint8_t (*functionPtr)(uint8_t), uint32_t period, enum task_state_t state)
{
    bool success = false;
    /*  index variable */
    uint8_t i;
    uint8_t size;
    
    /* do sanity check on state variable */
    assert (state < task_state_t_size);
    
    #ifdef NORTOS_DEBUG
        printf("\r\n");
        printf("create_task()\r\n");
    #endif
    
    /* must disable interrupts to insure atomic access to task control block
       which is accessed below, but could otherwise also be accessed inside
       the timer interrupt (i.e., non-atomically)
    */
    disable_interrupts();
    
    /* search for invalid task entry */
    for (i = 0; i < MAX_TASKS; i++)
    {
        if (tasks[i].state == INVALID)
        {
            success = true;
            break;
        }
    }
    
    /* invalid task entry found? */
    if (success)
    {
        /* set task to specified starting state */
        tasks[i].state = state;
        
        /* set pointer to function */
        tasks[i].functionPtr = functionPtr;
        
        /* assign process id */
        tasks[i].pid = (uint32_t) functionPtr;
        
        #ifdef NORTOS_DEBUG
            printf("tasks[%i].pid = %lu\r\n", i, tasks[i].pid);
        #endif
        
        /* set task period */
        tasks[i].period = period;
        tasks[i].delay  = period;
        
        #ifdef NORTOS_DEBUG
            printf("tasks[%i].period = %u\r\n", i, tasks[i].period);
            printf("tasks[%i].delay  = %u\r\n", i, tasks[i].delay);
        #endif
    }
    
    /* re-enable interrupts as quickly as possible */
    enable_interrupts();
    
    if (success)
        /* indicate success */
        return 0;
    
    /* indicate failure */
    return -1;
}

/**
 * @name    Delete Task
 * @brief   Remove specified task from task control block.
 *
 * Remove specified task from task control block.
 *
 * @param [in] functionPtr  Pointer to function which should be removed as a task.
 *
 * @retval 0   If deletion is successful.
 * @retval -1  If task is not found in task control block.
 *
 * Example Usage:
 * @code
 *    delete_task(&idle); // remove idle() task from task control block.
 * @endcode
 */
int delete_task(uint8_t (*functionPtr)(uint8_t))
{
    bool success = false;
    /* pid associated with task desired for removal */
    uint32_t searchPID;
    /* index variable */
    uint8_t i;
    
    /* obtain PID from address of function */
    searchPID = (uint32_t) functionPtr;
    
    disable_interrupts();
    
    /* search all tasks */
    for (i = 0; i < MAX_TASKS; i++)
    {
        /* appropriate PID found? */
        if (tasks[i].pid == searchPID)
        {
            /* set state to invalid */
            tasks[i].state = INVALID;
            success = true;
            break;
        }
    }
    
    enable_interrupts();
    
    /* deleted task successfully? */
    if (success)
        return 0;
    
    /* no */
    return -1;
}

/**
 * @name    Delay Task
 * @brief   Set task to a delayed state.
 *
 * If task was previously delayed (or was created in a delayed state)
 * and is then run by the scheduler, that means that the delay time
 * expired and the task was set to a ready state by the timer interrupt.
 * The task must be manually set to a delayed state by the user code
 * as desired.
 *
 * @param [in] functionPtr  Pointer to function which will be called when task is in READY state.
 * @param [in] period       Number of milliseconds to delay between task runs.
 * @param [in] state        Default state of task upon creation (see task_state_t).
 *
 * @retval  0  Successfully created task.
 * @retval -1  No entries were available in TCB.
 *
 * Example Usage:
 * @code
 *    void idle(void)
 *    {
 *      ...
 *      delay_task(&idle); // delay idle() task.
 *    }
 * @endcode
 */
int delay_task(uint8_t (*functionPtr)(uint8_t))
{
    bool success = false;
    /* pid associated with task desired for removal */
    uint32_t searchPID;
    /* index variable */
    uint8_t i;
    
    /* obtain PID from address of function */
    searchPID = (uint32_t) functionPtr;
    
    disable_interrupts();
    
    /* search all tasks */
    for (i = 0; i < MAX_TASKS; i++)
    {
        /* appropriate PID found? */
        if (tasks[i].pid == searchPID)
        {
            /* set state to delayed */
            tasks[i].state = DELAYED;
            
            /* reload delay */
            tasks[i].delay = tasks[i].period;
            
            /* store success */
            success = true;
            
            /* exit for loop */
            break;
        }
    }
    
    enable_interrupts();
    
    /* delayed task successfully? */
    if (success)
        return 0;
    
    /* no */
    return -1;
}

/**
 * @name    Change Task Delay
 * @brief   Change the delay period of a specified task.
 *
 * Change the delay period of a specified task.
 *
 * @param [in] functionPtr  Pointer to function which will be called when task is in READY state.
 * @param [in] period       Number of milliseconds to delay between task runs.
 *
 * @retval  0  Successfully changed task delay period.
 * @retval -1  No entries were found in the TCB matching that task.
 *
 * Example Usage:
 * @code
 *    change_task_delay(&idle, 3); // change task delay period to 3 seconds
 * @endcode
 */
int change_task_delay(uint8_t (*functionPtr)(uint8_t), uint32_t period)
{
    bool success = false;
    /* pid associated with task desired for removal */
    uint32_t searchPID;
    /* index variable */
    uint8_t i;
    
    /* obtain PID from address of function */
    searchPID = (uint32_t) functionPtr;
    
    disable_interrupts();
    
    /* search all tasks */
    for (i = 0; i < MAX_TASKS; i++)
    {
        /* appropriate PID found? */
        if (tasks[i].pid == searchPID)
        {
            /* change task period */
            tasks[i].period = period;
            tasks[i].delay  = period;
            success = true;
            break;
        }
    }
    
    enable_interrupts();
    
    /* changed task period successfully? */
    if (success)
        return 0;
    
    /* no */
    return -1;
}

/**
 * @name    Block Task
 * @brief   Lock task so that it blocks execution until it is unlocked.
 *
 * Change the delay period of a specified task.
 *
 * @param [in] functionPtr  Pointer to function which will be blocked.
 *
 * @retval  0  Successfully blocked task.
 * @retval -1  No entries were found in the TCB matching that task.
 *
 * Example Usage:
 * @code
 *    block_task(&idle); // task is no longer allowed to run
 * @endcode
 */
int block_task(uint8_t (*functionPtr)(uint8_t))
{
    bool success = false;
    /* pid associated with task desired for removal */
    uint32_t searchPID;
    /* index variable */
    uint8_t i;
    
    /* obtain PID from address of function */
    searchPID = (uint32_t) functionPtr;
    
    disable_interrupts();
    
    /* search all tasks */
    for (i = 0; i < MAX_TASKS; i++)
    {
        /* appropriate PID found? */
        if (tasks[i].pid == searchPID)
        {
            /* cause task to block */
            tasks[i].state = BLOCKED;
            break;
        }
    }
    
    enable_interrupts();
    
    /* task locked successfully? */
    if (success)
        return 0;
    
    /* no */
    return -1;
}

/**
 * @name    Unblock Task
 * @brief   Unlock task so that it is now set as delayed.
 *
 * If the intent is to unblock a task and run it on every
 * opportunity, then create/set delay period to 0. Block/unblock
 * could be used as a means of semaphore passing.
 *
 * @param [in] functionPtr  Pointer to function which will be unblocked.
 *
 * @retval  0  Successfully unblocked task.
 * @retval -1  No entries were found in the TCB matching that task.
 *
 * Example Usage:
 * @code
 *    unblock_task(&idle); // task is again allowed to run as time-trigggered
 * @endcode
 */
int unblock_task(uint8_t (*functionPtr)(uint8_t))
{
    bool success = false;
    /* pid associated with task desired for removal */
    uint32_t searchPID;
    /* index variable */
    uint8_t i;
    
    /* obtain PID from address of function */
    searchPID = (uint32_t) functionPtr;
    
    disable_interrupts();
    
    /* search all tasks */
    for (i = 0; i < MAX_TASKS; i++)
    {
        /* appropriate PID found? */
        if (tasks[i].pid == searchPID)
        {
            /* change state to delayed (i.e., time triggered) */
            tasks[i].state = DELAYED;
            /* restore period */
            tasks[i].delay = tasks[i].period;
            break;
        }
    }
    
    enable_interrupts();
    
    /* changed task period successfully? */
    if (success)
        return 0;
    
    /* no */
    return -1;
}

/**
 * @name    Scheduler
 * @brief   Handles running ready tasks from the task control block.
 *
 * Handles running ready tasks from the task control block.
 *
 * @param [in] Nothing  Takes no input.
 *
 * @retval currentTask   Return task index for task which should be run next.
 *
 * Example Usage:
 * @code
 *       currentTask = scheduler();
 *       printf("tasks[%i].pid = %lu\r\n", currentTask, tasks[currentTask].pid);
 *       returnedArg = tasks[currentTask].functionPtr(0);
 * @endcode
 */
/* scheduler */
int8_t scheduler(void)
{
    bool ok;
    static int8_t currentTask = -1;
    
    #ifdef NORTOS_DEBUG
        printf("\r\n");
        printf("scheduler()\r\n");
    #endif
    
    /* remain in loop until a ready-to-run task is found */
    ok = false;
    while (!ok)
    {
        /* try next task either right now or next time scheduler is called */
        currentTask++;

        /* return to first task if task entry hits upper limit  */
        if (currentTask >= MAX_TASKS)
        {
            currentTask = 0;
        }
        
        //#ifdef NORTOS_DEBUG
        //    printf("currentTask = %i being considered...\r\n", currentTask);
        //#endif
        
        /* access task control block atomically */
        disable_interrupts();
        
        /* ok=true if current task considered is ready,
           perform check this way so that disable/enable
           interrupts will always be executed predictably
        */
        ok |= (tasks[currentTask].state == READY);
        
        /* re-enable interrupts as quickly as possible */
        enable_interrupts();
    }
    
    #ifdef NORTOS_DEBUG
        printf("currentTask = %i\r\n", currentTask);
    #endif
    
    /* freeze up if current task index is negative
       FIXME: this may be fixable by not using the initial
       value of currentTask as -1 when function is first entered
       but logic must be changed to suit this special first case
    */
    assert(currentTask >= 0);
    
    /* return current task index */
    return currentTask;
}

/**
 * @name    noRTOS run
 * @brief   Run the scheduler and call the function indicated for running.
 *
 * This function also provides the capability to exit the scheduler calling loop.
 *
 * @param [in] nothing  Takes no input arguments.
 *
 * @retval nothing      Returns nothing.
 *
 * Example Usage:
 * @code
 *    // in main
 *    while(true)
 *    {
 *      nortos_run();
 *      // ...if we return scheduler was stopped purposely
 *    }
 * @endcode
 */
void nortos_run(void)
{
    /* by default nortos will be allowed to run */
    /* FIXME: allow noRTOS to be turned off */
    static bool noRTOSEnabled = true;
    uint8_t returnedArg;
    int8_t currentTask;
    
    /* run nortos? */
    while (noRTOSEnabled)
    {
        /* obtain current task from scheduler */
        currentTask = scheduler();
        
        #ifdef NORTOS_DEBUG
            printf("\r\n");
            printf("running...tasks[%i].pid = %lu\r\n", currentTask, tasks[currentTask].pid);
        #endif
        
        /* execute it */
        returnedArg = tasks[currentTask].functionPtr(0);
    }

}